/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	PrintSample

		MainWindow.cpp

			This module also includes the functions used to handle 
			the messages sent to the application's main window.

 ------------------------------------------------------------------

 Copyright (c) 2010 Microsoft Corporation. All rights reserved.

 THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
 PARTICULAR PURPOSE.

 ------------------------------------------------------------------
*/
#include "stdafx.h"
#include "PrintSample.h"

// Forward declaration of functions used only in this module
HRESULT WriteStatusToMainWindow (HWND hWnd);
PPRINTTHREADINFO ReleaseThreadInfo (PPRINTTHREADINFO threadInfo);


/** 
	<module>MainWindow.cpp</module>

	<summary>
		Message handler for the application's main window.</summary>

	<param name="hWnd">
		Handle to this window</param>

	<param name="message">
		Window message to process.</param>

	<param name="wParam">
		Paramter passed with window message. Usage depends on value 
		of message parameter.</param>

	<param name="lParam">
		Paramter passed with window message. Usage depends on value 
		of message parameter.</param>

	<returns>
		Depends on the message processed by the procedure.</returns>

	<remarks>
		<p>This procedure handles the following window messages.</p>
		<p>WM_NCCREATE: initializes the memory and data structures 
			used by the application.</p>
		<p>WM_CREATE: set the window title in the caption bar.</p>
		<p>WM_SETCURSOR: set the wait cursor while printing.</p>
		<p>WM_COMMAND: process the following menu commands.</p>
		<p>WM_COMMAND - ID_FILE_NEW (File->New): create a new test 
			document.</p>
		<p>WM_COMMAND - ID_FILE_PRINT (File->Print): print the test 
			document.</p>
		<p>WM_COMMAND - IDM_ABOUT (Help->About): display the about
			box.</p>
		<p>WM_COMMAND - IDM_EXIT (File->Exit): End the program.</p>
		<p>USER_CREATE_DOCUMENT: Call the function that creates a 
			new test document.</p>
		<p>USER_DESTROY_DOCUMENT: Call the function that destroys
			the test document and frees the resources it used.</p>
		<p>WM_PAINT: Paint the main window.</p>
		<p>WM_DESTROY: End the program by posting a quit message.</p>
		<p>All other window messages are handled by the default 
			window procedure.</p>
		</remarks>
*/
LRESULT CALLBACK 
WndProc(
		HWND hWnd, 
		UINT message, 
		WPARAM wParam, 
		LPARAM lParam
)
{
	PPRINTTHREADINFO		threadInfo = NULL;
	LPCREATESTRUCT			createStruct = NULL;

	switch (message)
	{
	case WM_NCCREATE:
		// Create and initialize the structures
		//  that will be used in this program.
		createStruct = (LPCREATESTRUCT)lParam;
		threadInfo = (PPRINTTHREADINFO)HeapAlloc(
						GetProcessHeap(),
						PRINT_SAMPLE_HEAP_FLAGS, 
						sizeof(PRINTTHREADINFO));

		if (NULL != threadInfo)
		{
			SetWindowLongPtr (hWnd, GWL_USERDATA, (LONG)threadInfo);
			threadInfo->applicationInstance = createStruct->hInstance;
			// The structure was allocated so return TRUE
			//  to continue creating the application's main window.
			return (LRESULT)TRUE;
		}
		else
		{
			// Unable to allocate the application's
			//  data structure so the program cannot
			//  continue.
			return (LRESULT)FALSE;
		}
		
	case WM_CREATE:
		// Get the pointer to the application's data structure.
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);
		if (NULL != threadInfo)
		{
			// Set the title bar text (in case it's not set during window creation).
			createStruct = (LPCREATESTRUCT)lParam;
			SetWindowText(hWnd, createStruct->lpszName);
			
			// Load the normal and the wait cursors.
			threadInfo->waitCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
			threadInfo->normalCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));

			// Create the font used in the main window.
			threadInfo->windowFont = CreateFont (
										MAIN_WINDOW_FONT_SIZE,
										0,
										0,
										0,
										FW_DONTCARE, 
										FALSE,
										FALSE,
										FALSE,
										ANSI_CHARSET,
										OUT_OUTLINE_PRECIS,
										CLIP_DEFAULT_PRECIS, 
										DEFAULT_QUALITY,
										DEFAULT_PITCH, 
										L"Arial");
			
			// Everything has been initialized so return 0 
			//  to indicate success.
			return (LRESULT)0L;
		}
		else
		{
			// No application data structure was found so return
			//  -1 to indicate failure.
			return (LRESULT)-1L;
		}

	case WM_SETCURSOR:
		// Get the pointer to the application's data structure.
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);
		
		if (NULL != threadInfo)
		{
			// Only set the cursor if the window is not minimized.
			if (!IsIconic(hWnd))
			{
				// If the printing thread is active, then
				//  set the main window cursoe to the wait 
				//  cursor otherwise use the normal cursor.
				if (NULL == threadInfo->printThreadHandle) 
				{
					SetCursor (threadInfo->normalCursor);
				}
				else
				{
					SetCursor (threadInfo->waitCursor);
				}
				// Indicate that no further processing is necessary
				//  for this message.
				return (LRESULT)TRUE;
			}
			else
			{
				// Indicate that this message should be processed
				//  by the default windows procedure.
				return (LRESULT)FALSE;
			}
		}
		else
		{
			//  Indicate that this message should be processed
			//  by the default windows procedure.
			return (LRESULT)FALSE;
		}

	case WM_COMMAND:
		// Get the pointer to the application's data structure
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);

		if (NULL != threadInfo)
		{
			int	wmId	= LOWORD(wParam);
			int wmEvent = HIWORD(wParam);;
			
			// Parse the menu selection
			switch (wmId)
			{
			case ID_FILE_NEW:
				// Create a new document.
				//
				// First, free the existing document content
				//  if it exists.
				if (NULL != threadInfo->documentContent)
				{
					// release any document-related memory
					SendMessage (hWnd, USER_DESTROY_DOCUMENT, 0L, 0L);
				}
				
				// Display the create new document dialog box
				DialogBox(
					threadInfo->applicationInstance, 
					MAKEINTRESOURCE(IDD_NEW_DOCUMENT), 
					hWnd, 
					NewDocumentDlgProc);

				// Indicate that no further processing is necessary
				//  for this message.
				return (LRESULT)0L;

			case ID_FILE_PRINT:
				// Get information about what to print.
				{
					HRESULT				hr = S_OK;
					BOOL				pdReturn = FALSE;
					PRINTDLG			pd = {0};

					// Print the current document if one
					//  has been created.
					if (NULL != threadInfo->documentContent)
					{
						// Inititialize the print dialog box's data structure.
						pd.lStructSize = sizeof( pd );
						pd.Flags = 
							// Return a printer device context
							PD_RETURNDC 
							// Don't allow separate print to file.
							| PD_HIDEPRINTTOFILE		
							| PD_DISABLEPRINTTOFILE 
							// Don't allow selecting individual document pages to print.
							| PD_NOSELECTION;

						// Display the printer dialog and retrieve the printer DC
						pdReturn = PrintDlg(&pd);
						
						// Check the return value.
						if (!pdReturn) hr = E_FAIL;

						if (S_OK == hr)
						{
							// A printer was returned so copy the information from 
							//  the dialog box structure and save it to the application's
							//  data structure.
							//
							//  Lock the handles to get pointers to the memory they refer to.
							PDEVMODE	devmode = (PDEVMODE)GlobalLock(pd.hDevMode);
							LPDEVNAMES	devnames = (LPDEVNAMES)GlobalLock(pd.hDevNames);

							// Free any old devmode structures and allocate a new one and
							// copy the data to the application's data structure.
							if (NULL != threadInfo->devmode)
							{
								HeapFree(GetProcessHeap(), 0L, threadInfo->devmode);
							}

							threadInfo->devmode = (LPDEVMODE)HeapAlloc(
								GetProcessHeap(), 
								PRINT_SAMPLE_HEAP_FLAGS, 
								devmode->dmSize);

							if (NULL != threadInfo->devmode) 
							{
								memcpy(
									(LPVOID)threadInfo->devmode,
									devmode, 
									devmode->dmSize);
							}
							else
							{
								// Unable to allocate a new structure so leave
								// the pointer as NULL to indicate that it's empty.
							}
					
							// Save the printer name from the devmode structure
							//  This is to make it easier to use. It could be
							//  used directly from the devmode structure.
							threadInfo->printerName = threadInfo->devmode->dmDeviceName;

							// Set the number of copies as entered by the user
							threadInfo->copies = pd.nCopies;

							// Some implementations might support printing more than
							// one package in a print job. For this program, only
							// one package (XPS document) can be printed per print job.
							threadInfo->packages = 1;
							
							// free allocated buffers from PRINTDLG structure
							if (NULL != pd.hDevMode) GlobalFree(pd.hDevMode);
							if (NULL != pd.hDevNames) GlobalFree(pd.hDevNames);

							// Display the print progress dialog box
							DialogBox(
								threadInfo->applicationInstance, 
								MAKEINTRESOURCE(IDD_PRINT_DLG), 
								hWnd, 
								PrintDlgProc);
						}
						else
						{
							// The user clicked cancel so there
							// is nothing more to do.
						}
					}
					else
					{
						// No document has been created, 
						//  display the warning to the user.
						MessageBoxWithResourceString (threadInfo->applicationInstance,
							hWnd, IDS_ERROR_NO_DOC, IDS_CAPTION_ERROR,
							(UINT)(MB_OK | MB_ICONINFORMATION));
					}
				}
				// Indicate that no further processing is necessary
				//  for this message.
				return (LRESULT)0L;

			case IDM_ABOUT:
				// Show the About box.
				DialogBox(
					threadInfo->applicationInstance, 
					MAKEINTRESOURCE(IDD_ABOUTBOX), 
					hWnd, 
					AboutDlgProc);

				// Indicate that no further processing is necessary
				//  for this message.
				return (LRESULT)0L;

			case IDM_EXIT:
				// Close the application
				//  Delete the current document
				SendMessage (hWnd, USER_DESTROY_DOCUMENT, 0L, 0L);

				// End the program
				DestroyWindow(hWnd);

				// Indicate that no further processing is necessary
				//  for this message.
				return (LRESULT)0L;

			default:
				// Let the default window procedure handle any
				//  other commands.
				return DefWindowProc(hWnd, message, wParam, lParam);
			}
		}
		else
		{
			// Unable to loceate the pointer to the info structure 
			//  so just pass this to the DefWindowProc.
			return DefWindowProc(hWnd, message, wParam, lParam);
		}

	case USER_CREATE_DOCUMENT:
		// Get the pointer to the application's data structure
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);

		if (NULL != threadInfo)
		{
			// Create new document using the parameters
			//  stored in the application's data structure.
			if (CreateTestDocumentContent (threadInfo, wParam) != S_OK)
			{
				// Display the error dialog because
				//  the new document wasn't created.
				MessageBoxWithResourceString (
					threadInfo->applicationInstance,
					hWnd, 
					IDS_ERROR_USER_DOC, 
					IDS_CAPTION_ERROR,
					(UINT)(MB_OK | MB_ICONINFORMATION));
			}
			else
			{
				// The new document was created so force the main window
				//  to redraw the information that describes the document
				//  and the steps that the user can take.
				InvalidateRect(hWnd,NULL,TRUE);
			}
		}

		// Indicate that no further processing is necessary
		//  for this message.
		return (LRESULT)0L;

	case USER_DESTROY_DOCUMENT:
		// Get the pointer to the application's data structure
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);

		if (NULL != threadInfo)
		{
			// Delete the document referenced in the application's
			//  data structure.
			DeleteDocumentContent (threadInfo);
		}

		// Indicate that no further processing is necessary
		//  for this message.
		return (LRESULT)0L;

	case WM_PAINT:
		// Write the current status of the application and the
		//  sample document to the main window of the application.
		WriteStatusToMainWindow(hWnd);

		// Indicate that no further processing is necessary
		//  for this message.
		return (LRESULT)0L;

	case WM_DESTROY:
		// Free the resources used by the applcation and
		//  end the application.

		// Get the pointer to the application's data structure
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);

		if (NULL != threadInfo)
		{
			// Close the Quit event, if one exists
			if (NULL != threadInfo->quitEvent) 
			{
				CloseHandle(threadInfo->quitEvent);
			}

			// There should be no document content at this point, but
			//  check to make sure.
			if (NULL != threadInfo->documentContent)
			{
				// ... and release it if there is.
				SendMessage (hWnd, USER_DESTROY_DOCUMENT, 0L, 0L);
			}
			// Clear the application's data structure and free the resources
			SetWindowLongPtr(
				hWnd, 
				GWLP_USERDATA, 
				(LPARAM)ReleaseThreadInfo(threadInfo));
		}
		// Post the quit message
		PostQuitMessage(ERROR_SUCCESS);

		// Indicate that no further processing is necessary
		//  for this message.
		return (LRESULT)0L;

	case WM_QUERYENDSESSION:
		// Yes, this program will process a WM_ENDSESSION message
		return (LRESULT)true;

	case WM_ENDSESSION:
 		// Get the pointer to the application's data structure
		threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);
		if (NULL != threadInfo)
		{
			// There is a valid pointer so see if there's any activity.
			if ((NULL != threadInfo->printThreadHandle) && (NULL != threadInfo->quitEvent))
			{
				// There's a print job in progress so stop it.
				SetEvent (threadInfo->quitEvent);

				// Wait for thread to exit
				// Return status is not important because the 
				//  system is shutting down so we'll need to clean up
				//  whether the thread stops or not.
				WaitForSingleObject (threadInfo->printThreadHandle, THREAD_END_WAIT);
			}

			// Destroy the window
			SendMessage (hWnd, WM_DESTROY, 0L, 0L);
		}
		return (LRESULT)0L;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
}

/** 
	<module>MainWindow.cpp</module>

	<summary>
		Writes the current status information
		to the main window</summary>

	<param name="hWnd">
		A handle to the main window.</param>

	<returns>
		HRESULT</returns>


*/
HRESULT WriteStatusToMainWindow (HWND hWnd)
{
	PAINTSTRUCT				ps;
	HDC						hdc;
	PPRINTTHREADINFO		threadInfo = NULL;
	LPWSTR					formatString = NULL;
	int						formatStringLength = 0;
	LPWSTR					displayString = NULL;
	int						displayStringLength = 0;
	TEXTMETRIC				textMetric = {0};
	int						indentValue = 20;
	int						rowHeight = 20;
	int						rowValue = 0;
	HFONT					thisFont = NULL;
	int						helpStringID  = 0;

	threadInfo = (PPRINTTHREADINFO)GetWindowLongPtr(hWnd, GWLP_USERDATA);
	
	if (NULL != threadInfo)
	{
		// redraw the client area of the window
		hdc = BeginPaint(hWnd, &ps);

		if (NULL != threadInfo->windowFont)
		{
			SelectObject(hdc, threadInfo->windowFont);
		}
		else
		{
			// use default font
		}

		// describe current document
		if (NULL != threadInfo->documentContent)
		{
			int	formatStringLength = 0;
			// there is a document so find out how many pages it has
			if (threadInfo->documentContent->pages != 1) 
			{
				formatStringLength = GetAllocedResourceString (threadInfo->applicationInstance, IDS_DOCUMENT_PAGES, &formatString);
			}
			else 
			{
				formatStringLength = GetAllocedResourceString (threadInfo->applicationInstance, IDS_DOCUMENT_PAGE, &formatString);
			}
			if (0 < formatStringLength)
			{
				// make room for inserted page count
				displayStringLength = formatStringLength * 2;
				displayString = (LPWSTR)HeapAlloc (GetProcessHeap(), PRINT_SAMPLE_HEAP_FLAGS, (displayStringLength * sizeof(WCHAR)));
				if (NULL != displayString)
				{
					displayStringLength = swprintf_s(
							displayString,
							displayStringLength,
							formatString,
							threadInfo->documentContent->pages);
					if (displayStringLength <= 0)
					{
						// unable to create formatted string
					}
				}
				else
				{
					// unable to alloc display string
				}
				HeapFree (GetProcessHeap(), 0L, formatString);
			}
			else
			{
				// unable to find ro alloc format string
			}
		}
		else
		{
			// no document yet
			displayStringLength = GetAllocedResourceString (threadInfo->applicationInstance, IDS_NO_DOCUMENT, &displayString);
		}

		if (GetTextMetrics(hdc, &textMetric))
		{
			indentValue = textMetric.tmAveCharWidth * 2;
			rowValue = int((float)textMetric.tmHeight * 1.2f);
		}
		else
		{
			// use arbitrary default values set as initialization
		}
		
		// write page info to first line
		rowValue = rowHeight;
		TextOut(hdc, indentValue, rowValue, displayString, displayStringLength);
		HeapFree (GetProcessHeap(), 0L, displayString);
		
		// skip a space between the document status and the command help
		rowValue += rowHeight;	

		for (helpStringID = IDS_HELP_1; helpStringID <= IDS_HELP_3; helpStringID++)
		{
			rowValue += rowHeight;	
			// write help info to next line if no document has been loaded
			displayStringLength = GetAllocedResourceString (threadInfo->applicationInstance, helpStringID, &displayString);
			if (displayStringLength > 0)
			{
				TabbedTextOut(hdc, indentValue, rowValue, displayString, displayStringLength, 0, NULL, 0);
				HeapFree (GetProcessHeap(), 0L, displayString);
			}
		}

		EndPaint(hWnd, &ps);
	}

	return S_OK;
}

/** 
	<module>MainWindow.cpp</module>

	<summary>
		Deletes a memory used by the application and frees the 
		resources associated with it.</summary>

	<param name="threadInfo">
		The application-specific structure that describes the current document and print job.</param>

	<returns>
		A NULL pointer.</returns>


*/
PPRINTTHREADINFO ReleaseThreadInfo (PPRINTTHREADINFO threadInfo)
{
	if (NULL != threadInfo)
	{
		if (NULL != threadInfo->devmode)
		{
			HeapFree(GetProcessHeap(),0L,(LPVOID)threadInfo->devmode);
			threadInfo->devmode = NULL;
		}

		if (NULL != threadInfo->windowFont)
		{
			DeleteObject (threadInfo->windowFont);
			threadInfo->windowFont = NULL;
		}

		// printerName is released with devmode
		// printerFile is released with devmode
		
		if (NULL != threadInfo->xpsFactory)
		{
			threadInfo->xpsFactory->Release();
			threadInfo->xpsFactory = NULL;
		}

		if (NULL != threadInfo->fontResource)
		{
			threadInfo->fontResource->Release();
			threadInfo->fontResource = NULL;
		}

		if (NULL != threadInfo->packageWriter)
		{
			threadInfo->packageWriter->Release();
			threadInfo->packageWriter = NULL;
		}

		if (NULL != threadInfo->printJobStream)
		{
			threadInfo->printJobStream->Release();
			threadInfo->printJobStream = NULL;
		}

		if (NULL != threadInfo->printJob)
		{
			threadInfo->printJob->Release();
			threadInfo->printJob = NULL;
		}

		HeapFree(GetProcessHeap(),0L,(LPVOID)threadInfo);
	}
	return NULL;
}